package com.dl.filter;

import com.dl.utils.ApplicationContextUtils;
import com.dl.utils.ConstantField;
import lombok.extern.log4j.Log4j2;
import org.apache.shiro.session.ExpiredSessionException;
import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.DefaultSessionKey;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.springframework.data.redis.core.RedisTemplate;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.util.*;

/**
 * 在线并发登录控制
 * 同一用户后登陆踢出前面（默认）的用户
 * @author DL
 * @create 2021-6-4 16:56
 */
@Log4j2
@SuppressWarnings("unchecked")
public class KickOutUserFilter extends AccessControlFilter {

    private String kickOutUrl; //踢出后到的地址
    private int maxSession; //同一个帐号最大会话数 默认1
    private final DefaultWebSessionManager sessionManager;

    public void setMaxSession(int maxSession) {
        this.maxSession = maxSession;
    }

    public KickOutUserFilter(DefaultWebSessionManager sessionManager) {
        this.sessionManager = sessionManager;
    }

    private RedisTemplate<String, String> getRedisTemplate(){
        //获取redis操作对象
        return (RedisTemplate<String, String>) ApplicationContextUtils.getBean("redisTemplate");
    }

    //表示是否允许访问；mappedValue就是[urls]配置中拦截器参数部分，如果允许访问返回true，否则false；
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        return false;
    }

    //表示访问拒绝时是否自己处理，如果返回true表示自己不处理且继续拦截器链执行，返回false表示自己已经处理了（比如重定向到另一个页面）。
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) {

        // 1.判断是否是登录用户
        Subject subject = getSubject(request, response);
        if(!subject.isAuthenticated() && !subject.isRemembered()) {
            //如果没有登录，直接进行之后的流程
            return true;
        }

        // 2.创建队列 k:username v:sessionId
        Session session = subject.getSession();
        String username = (String) subject.getPrincipal();
        String sessionId = (String) session.getId();

        List<String> range = getRedisTemplate().opsForList().range(ConstantField.KICK_OUT_SESSION + username, 0, -1);
        assert range != null;
        Deque<Object> deque = new LinkedList<>(range);

        log.debug("sessionId:{}",sessionId);


        //如果队列里没有此sessionId，放入队列
        if(!deque.contains(sessionId) ) {//&& session.getAttribute("kickOut") == null
            deque.addLast(sessionId);//功能和push相同
            //更新redis中队列缓存
            getRedisTemplate().opsForList().rightPush(ConstantField.KICK_OUT_SESSION + username, sessionId);
        }

        //如果队列大小超过在线人数控制
        if (deque.size() > maxSession){
            //获得队列第一个存储的sessionId
            String kickOutSessionId = (String) deque.getFirst();
            //移除队列第一个
            deque.removeFirst();
            try {
                //更新redis中队列缓存
                getRedisTemplate().opsForList().leftPop(ConstantField.KICK_OUT_SESSION + username);
                //通过kickOutSessionId获得用户session
                Session kickOutSession = sessionManager.getSession(new DefaultSessionKey(kickOutSessionId));
                //删除该用户的session
                sessionManager.getSessionDAO().delete(kickOutSession);
            } catch (UnknownSessionException e) {
                log.info("session已经失效");
            } catch (ExpiredSessionException e) {
                log.info("session已经过期");
            }
        }

        return true;
    }
}
